"""
面向对象编程之类 介绍
"""

# 1. 开门示例


class SupperMan:
    """
    类说明文档
    """

    # 定义初始化方法，在实例创建的时候判断该方法是否存在，如果存在则执行（self 必写）
    def __init__(self, name, age):
        print(' ---> 初始化开始')
        # 定义实例属性（也称为动态属性）
        self.name = name
        print(self.name)
        self.sex = 1
        print(self.sex)
        self.age = age
        print(self.age)
        self.single = False
        print(self.single)
        print('初始化结束 <---')

    # 定义实例方法（注意和后面介绍的类方法和静态方法做对比）。类中方法如无特别规定，都是以 self 作为第一个参数。self 表示的是引用类的当前实例
    def get_info(self):
        return self.__dir__()

    def get_age_info(self, sex_name):
        if self.age < 20:
            return sex_name + '少年'
        if 20 <= self.age < 40:
            return sex_name + '青年'
        if 40 <= self.age < 60:
            return sex_name + '中年'
        else:
            return sex_name + '老年'


# 创建实例
man = SupperMan('美国队长', 10)
# 通过 内置函数 dir 可知， 属性 'name', 'sex', 'single'，'age' 以及 定义的方法 'get_age_info', 'get_info' 已经出现在使用文档中
print(dir(man))
# 得到属性值
print(man.age)
print(man.name)
# 调用方法
info = man.get_info()
print(info)
print(man.__dir__())
# 调用方法并传入参数
print(man.get_age_info('男'))
# 创建实例
supper_man = SupperMan('真田小队长', 30)
print(supper_man.sex)
print(supper_man.name)
print(supper_man)
print(supper_man.get_info())
# 也可以用类来调用方法。需要注意：此时必须要指定实例。因为通过实例调用方法过程 self 是隐式的指向自己；而如果通过类则不知道 self 是谁，所以必须指定
age_info = SupperMan.get_age_info(supper_man, '女')
print(age_info)


# 1.1 如果要定义一个没有任何实现的类，可以使用 pass 关键字
class Pass:
    pass


print(dir(Pass))
p = Pass()
print(type(p))
print(p)


# 2. 类属性，也称为静态属性（注意和 1. 中的实例属性对比）。类属性只有通过类才能修改，实例也有类属性，但是不能修改类属性


class Foo:
    """
    这是说明文档
    """
    lang = 'python'


# 打印类使用说明
print(dir(Foo))

# 打印类属性值
print(Foo.lang)

# 打印实例中的类属性值
f = Foo()
print(type(f))
print(dir(f))
print(f.lang)

# 修改类的类属性 lang 属性值，同时会修改已经实例化的实例对应的类属性
Foo.lang = 'java'
print(f.lang)
print(Foo.lang)
# 修改类的类属性 lang 属性值后创建是实例也发生了改变
foo = Foo()
print(foo.lang)

# 修改实例的 lang 值并不能修改类的 lang 属性值
foo.lang = 'golang'
print(foo.lang)
print(Foo.lang)

# 3. 通过类增加类属性
Foo.group = '增加的类属性的值'
# 通过内置函数 dir 可以看到 group 已经在 使用文档中了
print(dir(Foo))
# 打印类属性值
print(Foo.group)
# 增加类属性之前实例化的对象也具备了增加的类属性
print(dir(foo))
print(foo.group)
# 增加类属性之后实例化的对象也具备了增加的类属性
foo1 = Foo()
print(dir(foo1))
print(foo1.group)

# 4. 给实例化对象增加实例属性
foo2 = Foo()
# 增加实例属性之前
print(dir(Foo))
print(dir(foo2))
# 增加实例属性之后，新增的属性在被新增操作的实例对象中存在，在对应的类中不存在，再次实例化后的新对象也不存在该新增属性
# 场景：工厂（即类）都没这个东西，新产生的产品（实例化的新对象）怎么会具备？产生后的产品（修改之前的实例）被怎么修改，已经和工厂无关了
foo2.age = 50
print(dir(Foo))
print(dir(foo2))
foo3 = Foo()
print(dir(foo3))

# 5. 在实例中将类属性修改后，通过 del 操作恢复 实例的类属性值
foo4 = Foo()
print(foo4.lang)
foo4.lang = 'GO'
print(foo4.lang)
del foo4.lang
print(foo4.lang)

# 6. 实例属性（也称为动态属性），不同实例的实例属性不同
foo5 = Foo()
# 对应类的说明文档
print(foo5.__doc__)
print(dir(foo5))
# 通过 __dict__ 查看实例属性
print(foo5.__dict__)
# 增加实例属性 age，再通过 __dict__ 查看实例属性，就包含了 age 实例属性
foo5.age = 50
print(foo5.__dict__)
# 删除实例属性 age，再通过 __dict__ 查看实例属性，就没包含 age 实例属性
del foo5.age
print(foo5.__dict__)

# 7. 类方法。通过 @classmethod 装饰器符号来表示方法为类方法。由于python类中只能有一个初始化方法，不能按照不同的情况初始化类，而类方法
# 可以用在模拟java定义多个构造函数的情况。虽然后面介绍的静态方法也可以实现上面功能，但静态方法每次都要写上类的名字，不方便


class ClassMethod:

    @classmethod
    # cls 即是这个类本身，通过下面的打印日志就可以看出来
    def get_input(cls, x):
        print(cls)
        print(x)


# 通过类直接调用类方法
ClassMethod.get_input(22)
# 通过实例调用类方法
method = ClassMethod()
method.get_input(33)

# 8. 静态方法。通过 @staticmethod 修饰符号来表示方法为静态方法。静态方法就是我们学过的函数，和函数唯一的区别是，静态方法定义在类这个空间
# （类命名空间）中，而函数则定义在程序所在的空间（全局命名空间）中。静态方法没有类似 self、cls 这样的特殊参数，因此 Python 解释器不会对
# 它包含的参数做任何类或对象的绑定。也正因为如此，类的静态方法中无法调用任何类属性和类方法


class StaticMethod:

    @staticmethod
    def get_add(x, y):
        return x + y


# 通过类直接调用
add = StaticMethod.get_add(4, 5)
print(add)
# 通过实例调用
static_method = StaticMethod()
get_add = static_method.get_add('33', '66')
print(get_add)

# 9. 小练习：实现一个功能类，给定两个日期，计算两者之间的天数和周数
# 由于使用到了第三方工具包 dateutil，所以需要自己引入。在终端中执行（注意不需要进入Python环境）：pip3 install python-dateutil 即可。

# pip3 拓展：
# ① 这些第三方工具包默认镜像来源是：https://pypi.org/project/pip/。如果觉得太慢可以指定国内提供的镜像源，例如清华大学的源，地址为：
#   https://pypi.tuna.tsinghua.edu.cn/simple 。分为临时使用和设置默认使用两种：
#       以 request 工具包为例，临时使用国内的镜像源：pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple requests
#       设置默认源：pip3 install pip -U
#                 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple ；
# ② 安装时还可以指定安装版本，例如需要安装 requests 工具包，安装版本为 2.25.1 ：pip3 install requests==2.25.1 ；
# ③ 查看已经安装的包：pip3 list ；
# ④ 对现有安装包升级，以 requests 工具包为例：pip3 install --upgrade requests。当然也可以指定版本号升级，例如：
#    pip3 install --upgrade requests==2.25.1 ；
# ⑤ 对现有安装包卸载，以 requests 工具包为例：pip uninstall requests ；

import datetime
from dateutil import rrule


class BetDate:

    # 初始化方法
    def __init__(self, start_date, stop_date):
        self.start = datetime.datetime.strptime(start_date, '%Y, %m, %d')
        self.stop = datetime.datetime.strptime(stop_date, '%Y, %m, %d')

    # 定义获取天数的方法
    def get_days(self):
        d = self.stop - self.start
        return d.days if d.days > 0 else False

    # 定义获取周数的方法
    def get_weeks(self):
        # 由于该方法参数个数大于需要使用的参数个数，所以需要通过指明参数名称 dtstart 和 until 来得到结果
        rrule_rrule = rrule.rrule(rrule.WEEKLY, dtstart=self.start, until=self.stop)
        return rrule_rrule.count()


date = BetDate('2019, 5, 1', '2021, 3, 19')
print(date.start)
print(date.stop)
print(date.get_days())
print(date.get_weeks())

# 10. 给定一个 以 '-' 连接的日期字符串，判断是否合法


class DateRule:

    # 假设该类的初始化方法是以年、月、日的整型形式进行的
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # 通过类方法实现类似Java构造函数的作用
    @classmethod
    def format(cls, date_str):
        # 通过特殊函数map，得到字符串中的单个字符串转换成的整型
        year, month, day = map(int, date_str.split('-'))
        # 使用 cls 调用初始化方法，得到类的实例
        d = cls(year, month, day)
        return d

    # 通过定义一个静态方法来判断输入日期字符串是否合法
    @staticmethod
    def is_date(year, month, day):

        # 通过 and 表达式，判断日期值是否合法。不合法返回 False，合法则返回日期值对应 '-' 字符串格式
        day_ = year <= 9999 and 0 < month <= 12 and 1 <= day < 33 and (str.format('{0}-{1}-{2}', year, month, day))
        return str.format('判断结果：{0}', day_)


# 通过类方法 format 得到类的实例，相当于 Java 中的构造方法
rule_format = DateRule.format('2020-03-11')
print(type(rule_format))
# 调用静态方法
is_date = DateRule.is_date(rule_format.year, rule_format.month, rule_format.day)
print(is_date)
# 构造一个错误的日期实例，将返回 False
r = DateRule.format('2020-14-03')
print(r.is_date(r.year, r.month, r.day))
